package com.rxyb.wechat.utils;

import com.alibaba.fastjson.JSONObject;
import com.rxyb.toolize.RxrcHttpUtils;
import com.rxyb.toolize.VO;
import com.rxyb.wechat.constants.MyConstants;
import com.rxyb.wechat.utils.bean.LocationResult;
import com.rxyb.wechat.utils.bean.WechatOpenVo;
import com.rxyb.wechat.utils.bean.WechatPubAcctVo;
import com.rxyb.wechat.utils.bean.WechatUserInfoVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.math.BigDecimal;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Optional;

/**
 * 微信工具
 *
 * @coder duyh,@auth duyh
 * @date Nov 20, 2018
 */
@Slf4j
@Service
public class WechatToolsClient {

    @Autowired
    private MyConstants myConstants;

    /**
     * 获取access_token
     *
     * @param appId
     * @param appSecret
     * @return
     */
    public Optional<String> getAccessToken(String appId, String appSecret) {
        try {
            String url = myConstants.getAccessToken().replace("${APPID}", appId).replace("${APPSECRET}", appSecret);
            String result = RxrcHttpUtils.executeGet(url);
            log.info("微信result:【{}】", result);
            JSONObject json = JSONObject.parseObject(result);
            String accessToken = json.getString("access_token");
            if (VO.isNotBlank(accessToken)) {
                return Optional.of(accessToken);
            }
        } catch (Exception e) {
            log.error("获取access_token异常：", e);
        }
        return Optional.empty();
    }

    /**
     * 公众号调用微信JS接口的临时票据
     *
     * @param accessToken
     * @return
     */
    public Optional<String> getJSApiTicket(String accessToken) {
        try {
            String url = myConstants.getJsapiTicket().replace("${ACCESS_TOKEN}", accessToken);
            String result = RxrcHttpUtils.executeGet(url);
            log.info("微信result:【{}】", result);
            JSONObject json = JSONObject.parseObject(result);
            return Optional.of(json.getString("ticket"));
        } catch (Exception e) {
            log.error("公众号调用微信JS接口的临时票据异常：", e);
            return Optional.empty();
        }
    }

    /**
     * 获取用户信息
     *
     * @param accessToken
     * @param openId
     * @return
     */
    public Optional<WechatUserInfoVo> getUserInfo(String accessToken, String openId) {
        try {
            String url = myConstants.getObtainAttention().replace("${ACCESS_TOKEN}", accessToken).replace("${OPENID}", openId);
            String result = RxrcHttpUtils.executeGet(url);
            log.info("微信result:【{}】", result);
            return Optional.of(JSONObject.parseObject(result, WechatUserInfoVo.class));
        } catch (Exception e) {
            log.error("获取用户信息异常：", e);
            return Optional.empty();
        }
    }

    /**
     * 根据code获取openid小程序
     *
     * @param code
     * @return
     */
    public Optional<WechatOpenVo> getOpenIdForMiniProj(String code, String appid, String secret) {
        try {
            String reqUri = myConstants.getObtainMiniprojOpenid().replace("${APPID}", appid).replace("${SECRET}", secret).replace("${CODE}", code);
            log.info("微信reqUri:【{}】", reqUri);
            String result = RxrcHttpUtils.executeGet(reqUri);
            log.info("微信result:【{}】", result);
            return Optional.of(JSONObject.parseObject(result, WechatOpenVo.class));
        } catch (Exception e) {
            log.error("根据code获取openid小程序异常：", e);
            return Optional.empty();
        }
    }

    /**
     * 根据code获取openid公众号
     *
     * @param code
     * @return
     */
    public Optional<WechatPubAcctVo> getOpenIdForPubAcct(String code, String appid, String secret) {
        try {
            String reqUri = myConstants.getObtainPubacctOpenid().replace("${APPID}", appid).replace("${SECRET}", secret).replace("${CODE}", code);
            log.info("微信reqUri:【{}】", reqUri);
            String result = RxrcHttpUtils.executeGet(reqUri);
            log.info("微信result:【{}】", result);
            return Optional.of(JSONObject.parseObject(result, WechatPubAcctVo.class));
        } catch (Exception e) {
            log.error("根据code获取openid公众号异常：", e);
            return Optional.empty();
        }
    }

    /**
     * 根据微信openId 获取用户是否订阅
     *
     * @param openId 微信openId
     * @return 是否订阅该公众号标识
     */
    public boolean subscribeState(String accessToken, String openId) {
        String url = myConstants.getObtainAttention().replace("${ACCESS_TOKEN}", accessToken).replace("${OPENID}", openId);
        String result = RxrcHttpUtils.executeGet(url);
        log.info("微信result:【{}】", result);
        JSONObject json = JSONObject.parseObject(result);
        if (VO.isBlank(json.getString("errmsg"))) {
            // 0代表此用户没有关注该公众号 1表示关注了该公众号。
            Integer state = json.getInteger("subscribe");
            if (null != state && state.equals(1)) {
                return true;
            }
            log.debug("用户取消了订阅，openId=【{}】", openId);
        } else {
            log.error("获取用户是否订阅通讯失败，返回false：【{}|{}】", openId, result);
        }
        return false;
    }


    /**
     * 发送模板消息
     *
     * @param accessToken
     * @param templateMessage
     * @return
     */
    public Optional<String> sendTemplate(String accessToken, String templateMessage) {
        try {
            String url = myConstants.getSendTemplateMsg().replace("${ACCESS_TOKEN}", accessToken);
            return Optional.of(RxrcHttpUtils.executePost(url, templateMessage));
        } catch (Exception e) {
            log.error("发送模板消息异常：", e);
            return Optional.empty();
        }
    }

    /**
     * https://lbs.qq.com/service/webService/webServiceGuide/webServiceGeocoder
     * status 	number 	是 	状态码，0为正常
     * 310请求参数信息有误
     * 311Key格式错误
     * 306请求有护持信息请检查字符串
     * 110请求来源未被授权
     * message 	string 	是 	状态说明
     * result 	object 	是 	地址解析结果
     * location 	string 	是 	解析到的坐标
     * lat 	number 	是 	纬度
     * lng 	number 	是 	经度
     * address_components 	object 	是 	解析后的地址部件
     * province 	string 	是 	省
     * city 	string 	是 	市
     * district 	string 	是 	区，可能为空字串
     * street 	string 	是 	街道，可能为空字串
     * street_number 	string 	是 	门牌，可能为空字串
     * ad_info 	object 	是 	行政区划信息
     * adcode 			行政区划代码
     * similarity 	number 	是 	即将下线，由reliability代替
     * deviation 	number 	是 	即将下线，由level代替
     * reliability 	number 	是 	可信度参考：值范围 1 <低可信> - 10 <高可信>
     * 我们根据用户输入地址的准确程度，在解析过程中，将解析结果的可信度(质量)，由低到高，分为1 - 10级，该值>=7时，解析结果较为准确，<7时，会存各类不可靠因素，开发者可根据自己的实际使用场景，对于解析质量的实际要求，进行参考。
     * level 	number 	否 	解析精度级别，分为11个级别，一般>=9即可采用（定位到点，精度较高） 也可根据实际业务需求自行调整，完整取值表见下文。
     */
    public LocationResult getLocat(String addr) {
        myConstants.setFindAddr("https://apis.map.qq.com/ws/geocoder/v1/?");
        myConstants.setFindAddrKey("5PEBZ-4FLRP-BYXDF-LQTM3-AV5CT-3GFQQ");
        StringBuffer sb = new StringBuffer();
        String urlString = sb.append(myConstants.getFindAddr()).append("address=").append(addr).append("&key=").append(myConstants.getFindAddrKey()).toString();
        String res = "";
        try {
            URL url = new URL(urlString);
            java.net.HttpURLConnection conn = (java.net.HttpURLConnection) url.openConnection();
            conn.setDoOutput(true);
            conn.setRequestMethod("GET");
            java.io.BufferedReader in = new java.io.BufferedReader(new java.io.InputStreamReader(conn.getInputStream(), "UTF-8"));
            String line;
            while ((line = in.readLine()) != null) {
                res += line + "\n";
            }
            in.close();
        } catch (Exception e) {
            System.out.println("error in wapaction,and e is " + e.getMessage());
        }
        LocationResult lr = JSONObject.parseObject(res, LocationResult.class);
        //        System.out.println(JSONObject.toJSONString(lr));
        return lr;
    }

    /**
     * 下载媒体文件字节流
     */
    public Optional<byte[]> downloadMedia(String accessToken, String mediaId) {
        String reqUri = myConstants.getDownLoadMedia().replace("${ACCESS_TOKEN}", accessToken).replace("${MEDIA_ID}", mediaId);
        HttpURLConnection conn = null;
        try {
            URL url = new URL(reqUri);
            conn = (HttpURLConnection) url.openConnection();
            conn.setDoInput(true);
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(30000);
            conn.setReadTimeout(30000);
            BufferedInputStream bis = new BufferedInputStream(conn.getInputStream());
            ByteArrayOutputStream swapStream = new ByteArrayOutputStream();
            byte[] buff = new byte[1024];
            int rc = 0;
            while ((rc = bis.read(buff, 0, 100)) > 0) {
                swapStream.write(buff, 0, rc);
            }
            return Optional.of(swapStream.toByteArray());
        } catch (Exception e) {
            log.error("下载媒体文件字节流异常：", e);
        } finally {
            if (conn != null) {
                conn.disconnect();
            }
        }
        return Optional.empty();
    }

    /**
     * 获取距离
     *
     * @param longitudeA
     * @param latitudeA
     * @param longitudeB
     * @param latitudeB
     * @return
     */
    public static String getDistance(String longitudeA, String latitudeA, String longitudeB, String latitudeB) {
        if (VO.isNotBlank(latitudeA) || VO.isNotBlank(latitudeA) || VO.isNotBlank(longitudeB) || VO.isNotBlank(latitudeB)) {
            return null;
        }
        double radLat1 = rad(Double.parseDouble(latitudeA));
        double radLat2 = rad(Double.parseDouble(latitudeB));
        double a = radLat1 - radLat2;
        double b = rad(Double.parseDouble(longitudeA)) - rad(Double.parseDouble(longitudeB));
        double distance = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) + Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)));
        distance = distance * EARTH_RADIUS;
        distance = Math.round(distance * 10000) / 10000;
        // 四舍五入取整数
        BigDecimal bd = new BigDecimal(distance).setScale(0, BigDecimal.ROUND_HALF_UP);
        return bd.toString();
    }

    // 地球平均半径
    private static final double EARTH_RADIUS = 6378137;

    // 把经纬度转为度（°）
    private static double rad(double d) {
        return d * Math.PI / 180.0;
    }
}
